#include "Ast.h"
#include "ParserUtil.h"



extern const OpNode::OpInfo opInfo[];
/****************************************************************/

const Type* OpNode::typeCheck() {

    // mod operator
    bool bflag = false;
    bool bflagForAssign = false;
    bool uflag = false;
    const Type *l,*r;
    l=r=NULL;
    ExprNode *left,*right;
    left=right=NULL;
    Type *retType=NULL;
    if(arity()==2) {
        left = arg(0);
        right = arg(1);
        if(left && right) {
            l = left->typeCheck();
            r = right->typeCheck();

            /*if (opCode() < OpNode::ASSIGN ) {
                if(!l) {
                    string err="Incompatible type for argument 1 for operator `" ;
                    err.append(opInfo[opCode()].name_ );
                    err.append("'");
                    errMsg(err);
                }
                else if(!r) {
                    string err="Incompatible type for argument 1 for operator `" ;
                    err.append(opInfo[opCode()].name_ );
                    err.append("'");
                    errMsg(err);
                }
            }*/
            if(l && r) {
                //if (l->isSubType(*r) || r->isSubType(*l)) {
                    bflag =  true;
                //}
                //if (r->isSubType(*l))
                    //bflagForAssign = true;
            }
        }
    }
    if(arity() ==1) {
        left = arg(0);
        if(left) {
            l = left->typeCheck();
            if(l) {
            uflag = true;
            }
            else {
                if (opCode() < OpNode::ASSIGN ) {
                    string err="Incompatible type for argument 1 for operator `" ;
                    err.append(opInfo[opCode()].name_ );
                    err.append("'");
                    errMsg(err);
                }
            }
        }

    }
    //MOD operator
    if (opCode()==OpNode::MOD && bflag ==  true) {
        bool lflag,rflag;
        lflag=rflag=true;
       if(!Type::isInt(l->tag())) {
            string err="Incompatible type for argument 1 for operator `%'"	;
            errMsg(err,this);
            lflag= false;
        }
       if(!Type::isInt(r->tag())) {
            string err="Incompatible type for argument 2 for operator `%'"	;
             errMsg(err,this);
            rflag = false;
        }
        //else if(Type::isInt(l->tag()) && Type::isInt(r->tag()))  {
        if (lflag && rflag) {
            if (l->isSubType(*r)) {
                left->coercedType(r);
            }
            else if(r->isSubType(*l)) {
                right->coercedType(l);
            }
        }




        type(new Type(Type::INT));
        return type();
            //}

        //retType = new Type(Type::INT);
        //type(retType);
        //return type();
        }
    //Arithmetic operators
    if(opCode() >= OpNode::PLUS &&  opCode() <= OpNode::DIV && bflag ==  true) {
        bool lflag,rflag;
        lflag=rflag=true;
        if(!Type::isNumeric(l->tag())) {
            string err="Incompatible type for argument 1 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
            lflag=false;
        }
        if(!Type::isNumeric(r->tag())) {
            string err="Incompatible type for argument 2 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
            errMsg(err,this);
            rflag=false;
        }

        //else if ((Type::isNumeric(l->tag())) && (Type::isNumeric(r->tag()))) {

                const Type *t=NULL;
                if (l->isSubType(*r) || (rflag == true && lflag==false)) {
                    t = r;
                    left->coercedType(r);
                }
                else if(r->isSubType(*l) || ((rflag == false && lflag==true))) {
                    t = l;
                    right->coercedType(l);
                }
                else if (l->tag() == r->tag()){
                    t = l;
                }
                type((Type *)t);

                return type();
            //}

        //retType = new Type(Type::INT);
        //type(retType);
        //return type();


    }
    //Logical operators
    if(opCode() >= OpNode::AND &&  opCode() <= OpNode::OR && bflag ==  true) {


        if(!Type::isBool(l->tag())) {
            string err="Incompatible type for argument 1 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
        }
        if(!Type::isBool(r->tag())) {
            string err="Incompatible type for argument 2 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
        }
       // else if (Type::isBool(l->tag()) || Type::isBool(r->tag())) {
                //if (l->isSubType(*r) || r->isSubType(*l)) {}
                type(new Type(Type::BOOL));
                return type();

            //}

    }
    //Relational operators
    if(opCode() >= OpNode::EQ &&  opCode() <= OpNode::LE && bflag ==  true) {

        bool lflag,rflag;
        lflag=rflag=true;
        if (!(Type::isNumeric(l->tag()) || Type::isBool(l->tag()) || Type::isString(l->tag()))) {
            string err="Incompatible type for argument 1 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
            lflag=false;
        }
        if (!(Type::isNumeric(r->tag()) || Type::isBool(r->tag()) || Type::isString(r->tag()))) {
            string err="Incompatible type for argument 2 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
            rflag=false;
        }
        //else if ((Type::isNumeric(l->tag())) && (Type::isNumeric(r->tag()))) {
                /*const Type *t;
                if (l->isSubType(*r)) {
                    t = r;
                    //left->coercedType(r);
                }
                else if(r->isSubType(*l)) {
                    t = l;
                    //right->coercedType(l);
                }*/
                type(new Type(Type::BOOL));
                return type();

            //}

    }
    //Bitwise Operators
    if(opCode() >= OpNode::BITAND &&  opCode() <= OpNode::SHR && bflag ==  true) {
        bool lflag,rflag;
        lflag=rflag=true;
        if(!Type::isIntegral(l->tag())) {
            string err="Incompatible type for argument 1 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
            lflag=false;
        }
        if(!Type::isIntegral(r->tag())) {
            string err="Incompatible type for argument 2 for operator `" ;
			err.append(opInfo[opCode()].name_ );
			err.append("'");
             errMsg(err,this);
            rflag=false;
        }
        //else if (Type::isIntegral(l->tag())  && Type::isIntegral(r->tag())) {
        //if (rflag == true || lflag==true)  {
                const Type *t=NULL;
                if (l->isSubType(*r) && (rflag == true && lflag==true )) {
                    t = r;
                    left->coercedType(r);
                }
                else if(r->isSubType(*l) && (rflag == true && lflag==true )) {
                    t = l;
                    right->coercedType(l);
                }
                 else if (l->tag() == r->tag()){
                    t = l;
                }
                else if( (rflag == true && lflag==false) ) {
                    t = r;
                    //left->coercedType(r);
                }

                else if( (rflag == false && lflag==true) ) {
                    t = l;
                    //right->coercedType(l);
                }
                type((Type *)t);
                return type();
            //}

    }
    //Assignment operator
    if(opCode() == OpNode::ASSIGN) {
                if(r && l) {
                    //cout<<r->fullName()<<"\n";
                    //cout<<l->fullName();
                    if(r->tag() == l->tag()){
                        //type(new Type(Type::BOOL));
                        //return type();
                    }
                    else if (r->isSubType(*l)) {
                        right->coercedType(l);
                        //type(new Type(Type::BOOL));
                        //return type();
                    }
                    else
                    {
                        string err="Assigned expression must be a subtype of target";
                        errMsg(err, line(), column(),file().c_str());
                    }
                    type(new Type(Type::BOOL));
                    return type();
                }
    }
    //UMINUS operator
    if(opCode() == OpNode::UMINUS &&  uflag ==  true) {
        bool lflag=true;
        if (!(Type::isIntegral(l->tag()) || Type::isFloat(l->tag()))) {
            string err="Incompatible type for argument 1 for operator `-'" ;
			 errMsg(err,this);
			lflag = false;
        }
        if(lflag) {
        if (l->tag() == Type::UINT) {
                Type *t = new Type(Type::INT);
                left->coercedType(t);
                type((Type *)t);
            }
        else
            type((Type *)l);
        return type();
        }
        /*else {

        }*/

    }
    //NOT operator
    if(opCode() == OpNode::NOT &&  uflag ==  true) {
        if (Type::isBool(l->tag())) {

        }
        else {
            string err="Incompatible type for argument 1 for operator `!'" ;
			 errMsg(err,this);
        }
        type(new Type(Type::BOOL));
            return type();
    }
    //BITNOT operator
    if(opCode() == OpNode::BITNOT &&  uflag ==  true) {

        if (Type::isIntegral(l->tag())) {
        //if(l->tag() == Type::UINT) {

        }
        else {
            string err="Incompatible type for argument 1 for operator `~'" ;
			 errMsg(err,this);


        }
        Type *t = new Type(Type::UINT);
        if(Type::isSigned(l->tag())) {

			left->coercedType(t);
			type(t);
            return type();
        }
        else if (Type::isUnsigned(l->tag())) {
            type((Type *)l);
            return type();
        }
        else {
            type(t);
            return type();
        }




    }
    if(opCode() == OpNode::PRINT) {
        if(l) {
            if(!(Type::isNumeric(l->tag()) || Type::isString(l->tag()))) {
            string err="Incompatible type for argument in print" ;
			 errMsg(err,this);
            }
            else {
                type((Type *)l);
                return type();

            }

        }
    }
    //return retType;
    type((Type *)NULL);
    return NULL;


}
void OpNode::typePrint(ostream& os, int indent) const {

   if (opInfo[opCode_].prtType_ == OpNode::PREFIX) {
       if(coercedType())
                os<<"(" << coercedType()->name() <<") ";
		os << opInfo[opCode_].name_;
		if (arity_ > 0) {
			if (opInfo[opCode_].needParen_)
				os << '(';
			for (unsigned i=0; i < arity_-1; i++) {
				if (arg_[i])
					arg_[i]->typePrint(os, indent);
				else os << "NULL";
				os << ", ";
			}
			if (arg_[arity_-1])
				arg_[arity_-1]->typePrint(os, indent);
			else os << "NULL";
			if (opInfo[opCode_].needParen_)
				os << ") ";
		}
	}
	else if ((opInfo[opCode_].prtType_ == OpNode::INFIX) && (arity_ == 2)) {

        //if(opCode() >= OpNode::PLUS &&  opCode() <= OpNode::MOD) {
            if(coercedType())
                os<<"(" << coercedType()->name() <<") ";
	    //}
		if (opInfo[opCode_].needParen_)
			os << "(";
		if(arg_[0])
			arg_[0]->typePrint(os, indent);
		else os << "NULL";
		os << opInfo[opCode_].name_;
		if(arg_[1])
			arg_[1]->typePrint(os, indent);
		else os << "NULL";
		if (opInfo[opCode_].needParen_)
			os << ")";
	}
	else internalErr("Unhandled case in OpNnode::print");


    }

const Type* IfNode::typeCheck() {


    const Type* t=NULL;
    if(cond())
    t = cond()->typeCheck();

	if(t  &&  t->tag()!=Type::BOOL)
	{
		string err="Error:Boolean argument expected";
		errMsg(err, line(), column(),file().c_str());

	}
	if(thenStmt())
	{
		thenStmt()->typeCheck();
	}
	if(elseStmt())
	{
		elseStmt()->typeCheck();
	}
	//return NULL;
}
void IfNode::typePrint(ostream& os, int indent) const {
    os << "if (";
	cond_->typePrint(os,indent);
	os << ") ";
	if (then_) {
		then_->typePrint(os,indent);
		if (then_->stmtNodeKind() != StmtNode::COMPOUND) {
			os << ";";
			if (!else_) prtln(os,indent);
		}
	}
	if (else_) {
		if (then_->stmtNodeKind() != StmtNode::COMPOUND)
			prtln(os, indent);
		os << "else ";
		else_->typePrint(os,indent);
		if (else_->stmtNodeKind() != StmtNode::COMPOUND) {
			os << ";";
			prtln(os, indent);
		}
	}

}

const Type* RefExprNode::typeCheck() {
    //cout<<symTabEntry()->kind();
    //cout<<symTabEntry()->type()->name();
    if(symTabEntry())
    return symTabEntry()->type();
    else
    return NULL;


    }
void RefExprNode::typePrint(ostream& os, int indent) const {
    if(coercedType())
		os<<"(" << coercedType()->name() <<") ";


    if(symTabEntry() && symTabEntry()->type())
        os<<symTabEntry()->type()->fullName();

}



const Type* RuleNode::typeCheck() {

    if (pat())
        pat()->typeCheck();
    if(reaction())
    reaction()->typeCheck();
    //return NULL;

    }
void RuleNode::typePrint(ostream& os, int indent) const {
    if (pat_) {
		prtln(os,indent);
		pat_->typePrint(os,indent);
		os << "-->  ";
	}
	if (reaction_) {
		reaction_->typePrint(os,indent);
	};
	os << ";;";


    }

const Type* InvocationNode::typeCheck() {


	const Type *  retType = NULL;
	FunctionEntry * vte=	(FunctionEntry * )symTabEntry();
	 vector<const VariableEntry*>*  formals=vte->formals();

	/*if(params_ == NULL || (params_ ->size() != formals->size()))*/
	if( params_ && (params_ ->size() != formals->size()) || (params_== NULL && formals->size() > 0))
	{
		string err=itoa(formals->size()) + " arguments expected for " + symTabEntry()->name() ;
		errMsg(err, line(), column(), file().c_str());
		return NULL;

	}
	else if(params_)
	{
		//cout<<"\nInvocationNode TypeCheck   3\n";
		for(int i =0 ; i< formals->size();i++)
		{
			ExprNode* paramNode= ( ExprNode*)((*params_)[i]);
			const  Type * actualParamType= paramNode->typeCheck();
			if(!(actualParamType && formals->at(i) && formals->at(i)->type()))
                continue;

			if(actualParamType->fullName()==formals->at(i)->type()->fullName() )
			{

			}
			else if( actualParamType->isSubType(* (formals->at(i)->type())  )  )
			{
				paramNode->coercedType (formals->at(i)->type());
			}
			else
			{

				string err="Type mismatch for argument " + itoa(i+1)+ " to  " + symTabEntry()->name() ;
				errMsg(err, line(), column(),file().c_str());

			}
		}
	}
	if(vte->type())
	return vte->type()->retType();
	else
	return NULL;



}
void InvocationNode::typePrint(ostream& os, int indent) const {
    os << ste_->name() << "(";
	if (params_) {
		vector<ExprNode*>::const_iterator it;
		for (it=params_->begin(); ;) {
			if (*it) {
				(*it)->typePrint(os, indent);
			}
			++it;
			if (it!=(params_->end())) {
				os << ",";
			} else
				break;
		}
	}
	os << ")";


    }

const Type* PatNode::typeCheck() {
    switch(kind()) {
	case PRIMITIVE:
		break;
	case EMPTY:
		break;
	case NEG:
        if(hasSeqOps())
		{
			string err="Invalid use of pattern negation";
			errMsg(err, line(), column(),file().c_str());
		}
		//os << "!";
		pat1_->typeCheck();
		break;
	case SEQ:
		pat1_->typeCheck();
		//os << ":";
		pat2_->typeCheck();
		break;
	case OR:
		pat1_->typeCheck();
		//os << " \\/ ";
		pat2_->typeCheck();
		break;
	case STAR:
		pat1_->typeCheck();
		//os << "**";
		break;
	case UNDEFINED:
		break;
	};


    }
void PatNode::typePrint(ostream& os, int indent) const {
    os << "(";
	switch(kind()) {
	case PRIMITIVE:
		break;
	case EMPTY:
		break;
	case NEG:
		os << "!";
		pat1_->typePrint(os,indent);
		break;
	case SEQ:
		pat1_->typePrint(os,indent);
		os << ":";
		pat2_->typePrint(os,indent);
		break;
	case OR:
		pat1_->typePrint(os,indent);
		os << " \\/ ";
		pat2_->typePrint(os,indent);
		break;
	case STAR:
		pat1_->typePrint(os,indent);
		os << "**";
		break;
	case UNDEFINED:
		break;
	};
	os << ")";


    }

const Type* PrimitivePatNode::typeCheck() {
    if(!event())
        return NULL;
    Type *te = event()->type();
    vector<Type*>* argTypes = NULL;
    if (te)
        argTypes = te->argTypes();
    int formal_arg=0,actual_arg=0;
    if(argTypes)
        formal_arg = argTypes->size();
    else
        formal_arg = 0;

    if(params())
        actual_arg = params()->size();
    else
        actual_arg = 0;

    if(formal_arg > actual_arg) {
        string err="Event "+ee_->name() +" requires " +itoa(formal_arg)  + " arguments";
        errMsg(err, line(), column(),file().c_str());

    }
    if(params() && argTypes) {
        vector<VariableEntry*>::const_iterator it;
		vector<Type*>::const_iterator itType;
		/*for (it = params_->begin(),itType = argTypes->begin();it!=params_->end(),itType!=argTypes->end();) {
		    if(*it)
		    (*it)->typeCheck();
			if ((*itType) && (*it)) {
			    if((*it)->type() && (*itType)) {
                Type *ty = *itType;
			    if(!((*it)->type()->isSubType(*ty))){}

			    }
			}
			++it;
			++itType;
		}*/
		for (it = params_->begin();it!=params_->end();++it) {
		    if(*it)
		    (*it)->typeCheck();

		}

    }

    if(cond_)
        cond_->typeCheck();


    //return te;

}
void PrimitivePatNode::typePrint(ostream& os, int indent) const {
    if (!ee_) return;
	Type* type = ee_->type();
	vector<Type*>* argTypes = NULL;
	if (type) argTypes = type->argTypes();

	os << "(" << ee_->name();
	if (params_) {
		os << "(";
		vector<VariableEntry*>::const_iterator it;
		vector<Type*>::const_iterator itType;
		for (it = params_->begin(),itType = argTypes->begin();it!=params_->end();) {
			if ((*itType) && (*it)) {
				os << (*itType)->fullName() << " " <<  (*it)->name();
				//os << (*it)->type()->fullName() << " " <<  (*it)->name();
			}
			++it;
			++itType;
			if (it!=params_->end() && itType!=argTypes->end())
				os << ", ";
		}
		os << ")";
	} else {
		if (ee_->name()!="any")
			os << "()";
	}
	if (cond_) {
		os << "|";
		cond_->typePrint(os,indent);
	}
	os << ")";


}


const Type* WhileNode::typeCheck() {
    const Type* t=NULL;
    if(cond())
    t = cond()->typeCheck();

	if(t  &&  t->tag()!=Type::BOOL)
	{
		string err="Error:Boolean argument expected";
		errMsg(err, line(), column(),file().c_str());

	}
	if(whileStmt())
	{
		whileStmt()->typeCheck();
	}
}
void WhileNode::typePrint(ostream& os, int indent) const {
    os << "while (";
    if(cond())
    cond()->typePrint(os,indent);
    os << ") ";
    if(stmt_) {
        stmt_->typePrint(os,indent);
        if (stmt_->stmtNodeKind() != StmtNode::COMPOUND) {
			os << ";";
			prtln(os, indent);
        }
    }

    }


const Type* BreakStmtNode::typeCheck() {

    if(loop_num_ && !(Type::isInt(loop_num_->type()->tag()))) {
        string err="Integer value expected";
		errMsg(err, line(), column(),file().c_str());
    }

    }
void BreakStmtNode::typePrint(ostream& os, int indent) const {
    os << "break ";
    if(loop_num_)
         os<<loop_num_->type()->name();
    os<< ";";
			prtln(os, indent);

    }


const Type* ValueNode::typeCheck() {
        return type();



    }
void ValueNode::typePrint(ostream& os, int indent) const {
    if(coercedType())
        os<<"("<<coercedType()->name()<<")";
    os<<type()->name();

    }

const Type* ReturnStmtNode::typeCheck() {

    const Type *retType = NULL;

    if(expr_ && fun_) {
        retType = expr_->typeCheck();
        const Type *funcType=NULL;
        if(fun_->type())
            funcType = fun_->type()->retType();
        if(retType && funcType) {
            if(retType->fullName() == funcType->fullName()) {}
            else if (retType->isSubType(*funcType)){
            //type(new Type(funcType->tag()));
                expr_->coercedType(funcType);
                //return retType;
            }
            else if( funcType->tag() == Type::VOID ) {
                string err="No return value expected for a void function";
                errMsg(err, line(), column(),file().c_str());
            }
            else {
                string err="Return value incompatible with current function's type";
                errMsg(err, line(), column(),file().c_str());
            }
        }
    }
    return retType;

}


const Type* ExprStmtNode::typeCheck() {

    if(expr_)
    return expr_->typeCheck();
    else
    return NULL;

}


const Type* CompoundStmtNode::typeCheck() {
    list<StmtNode*>* sm = stmts_;
	list<StmtNode*>::iterator it;
	if(stmts()->size() > 0) {
	for(it = sm->begin();it != sm->end();++it) {
	    if(*it!=NULL) {
            (*it)->typeCheck();
	    }
	}
	}
	return NULL;





}
void CompoundStmtNode::typePrint(ostream& os, int indent) const {
    os << "{";
	if (stmts_ && stmts_->size()) {
		prtln(os,indent+STEP_INDENT);
		typePrintWithoutBraces(os,indent+STEP_INDENT);
	}
	os << "};";
	prtln(os,indent);


}
void CompoundStmtNode::typePrintWithoutBraces(ostream& os, int indent) const {
    if (stmts_ == NULL) return;
	list<StmtNode*>* sm = stmts_;
	list<StmtNode*>::iterator it = sm->begin();
	while(1) {
		if (*it) {
			(*it)->typePrint(os,indent);
			if ( (*it)->stmtNodeKind() == StmtNode::EXPR || (*it)->stmtNodeKind() == StmtNode::RETURN ) {
				os << ";";
				++it;
				if ( it == sm->end() )
					prtln(os,indent-STEP_INDENT);
				else
					prtln(os,indent);
				--it;
			}
			it++;
			if (it==sm->end())
				break;
		} else {
			break;
		}
	}


}

const Type * TempNode::typeCheck()  {
    return type();
}

void TempNode::typePrint(ostream& os, int indent) const {}

/*const Type* PrintStmtNode::typeCheck()  {
    if(cond())
    return cond()->typeCheck();
}

void PrintStmtNode::typePrint(ostream& os, int indent) const {
    os << "print(";
    if(cond())
    cond()->typePrint(os,indent);
    os << ");";

}*/
